package spreadsheet.ui;

import java.awt.Color;
import java.awt.Component;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import spreadsheet.FormulaParser;
import spreadsheet.SpreadSheet;
import spreadsheet.SpreadSheetCell;

/**
 * UI for Spreadsheet
 */
public class Table extends JTable {
    
    private int iSelectedRow = -1;
    private int iSelectedColumn = -1;
    private DefaultTableModel dtm;
    private SpreadSheet ssh = new SpreadSheet();
    public static int ROW_COUNT = 48;
    public static int COL_COUNT = 25;
    
    /**
     * Constructor for Table
     */
    public Table( SpreadSheet ssh ) {
        this( ssh != null ? ssh.getCells() : null );
    }
    
    /**
     * Constructor for Table
     */
    public Table( SpreadSheetCell[][] cells ) {
        setData( cells );
        initComponent();
    }
    
    /**
     * Constructor for Table
     */
    public Table() {
        SpreadSheetCell[][] cells = new SpreadSheetCell[ROW_COUNT][COL_COUNT + 1];//+1 for rows header
        ssh.setCells( cells );
        FormulaParser.getInstance().setSpreadSheet( ssh );
        dtm = new DefaultTableModel( cells, generateColumnNames( COL_COUNT + 1 ) );//48 rows and 24 + 1 columns by default
        initComponent();
    }
    
    private void initComponent() {
        setModel( dtm );
        addRowsHeader();
        setSelectionMode( ListSelectionModel.SINGLE_SELECTION );//the user can select only one cell for a formula
        setAutoResizeMode( JTable.AUTO_RESIZE_OFF );//this is needed for the horizontal scroll bar
        setMinSizeForColumns();
        
//        stash the coordinates of the cell last selected with the mouse and notify the status bar
        addMouseListener( new MouseAdapter() {

            @Override
            public void mouseReleased( MouseEvent e ) {
                iSelectedRow = Table.this.getSelectedRow();
                iSelectedColumn = Table.this.getSelectedColumn();
                notifyMainWindow();
            }
        });
//        stash the coordinates of the cell last selected with arrow keys and notify the status bar
        
        
        addKeyListener( new KeyAdapter() {
            @Override
            public void keyPressed( KeyEvent e ) {
                switch( e.getKeyCode() ) {
                    case KeyEvent.VK_RIGHT:
                        if( iSelectedColumn < dtm.getColumnCount() - 1 ) {
                            iSelectedColumn++;
                        }
                        break;
                    case KeyEvent.VK_LEFT:
                        if( iSelectedColumn > 0 ) {
                            iSelectedColumn--;
                        }
                        break;
                    case KeyEvent.VK_UP:
                        if( iSelectedRow > 0 ) {
                            iSelectedRow--;
                        }
                        break;
                    case KeyEvent.VK_ENTER:
                    	if( iSelectedRow == dtm.getRowCount() - 1 ) {
                        	iSelectedRow = -1;
                        	iSelectedColumn++;
                        }
                    case KeyEvent.VK_DOWN:
                    	if( iSelectedRow < dtm.getRowCount() - 1 ) {
                            iSelectedRow++;
                        }
                        break;
                    case KeyEvent.VK_HOME:
                        iSelectedColumn = 0;
                        break;
                    case KeyEvent.VK_END:
                        iSelectedColumn = dtm.getColumnCount() - 1;
                        break;
                    case KeyEvent.VK_PAGE_DOWN:  
                    		if( dtm.getRowCount() - 1 >iSelectedRow+ (int) (Table.this.getParent().getSize().getHeight() / Table.this.getRowHeight()))
                    		{	
                    			iSelectedRow =    iSelectedRow+ (int) (Table.this.getParent().getSize().getHeight() / Table.this.getRowHeight());
                    		}
                    		else
                    		{
                    			iSelectedRow = dtm.getRowCount() - 1;
                    		}
                    		break;
                    case KeyEvent.VK_PAGE_UP:  
                    		if(0 <  iSelectedRow- (int) (Table.this.getParent().getSize().getHeight() / Table.this.getRowHeight()))
                    		{
                    			iSelectedRow =   iSelectedRow- ((int) (Table.this.getParent().getSize().getHeight() / Table.this.getRowHeight()))-1;
                    		}
                    		else
                    		{
                    			iSelectedRow = 0;
                    		}
                    	break;
                }
                notifyMainWindow();
            }
        });
    }
    
    /**
     * Send the formula or the primitive to the last selected cell in this table
     */
    public void setFormulaInSelectedCell( String strValue ) {
        if( iSelectedRow >= 0 && iSelectedColumn >= 0 ) {
            String strType = SpreadSheetCell.CELL_TYPES_FORMULA;//by default is formula
            try {
                Double.parseDouble( strValue );
                strType = SpreadSheetCell.CELL_TYPES_PRIMITIVE;//if we can parse the value then it is a primitive
            } catch( Exception e ) {}
            SpreadSheetCell cell = new SpreadSheetCell( strType, strValue );
            setValueAt( cell, iSelectedRow, iSelectedColumn );
            ssh.setCell( cell, iSelectedRow, iSelectedColumn -1);
            if( strType.equals( SpreadSheetCell.CELL_TYPES_FORMULA ) ) {
                try {
                    if( FormulaParser.getInstance().parse( strValue ).isEmpty() ) {
                        ( ( MainWindow )SwingUtilities.getWindowAncestor( Table.this ) ).setStatus( 
                            "Error: formula not valid in cell (" + ( iSelectedRow + 1 ) + ", " + getColumnName( iSelectedColumn ) + ")" );
                        return;
                    }
                } catch( Exception ex ) {
                    ( ( MainWindow )SwingUtilities.getWindowAncestor( Table.this ) ).setStatus( 
                        "Error: " + ex.getLocalizedMessage() + " in cell (" + ( iSelectedRow + 1 ) + ", " + getColumnName( iSelectedColumn ) + ")" );
                    return;
                }
            }
            ( ( MainWindow )SwingUtilities.getWindowAncestor( Table.this ) ).setStatus( 
                        "Entered " + strType + " in cell (" + ( iSelectedRow + 1 ) + ", " + getColumnName( iSelectedColumn ) + ")" );
        } else {
            JOptionPane.showMessageDialog( SwingUtilities.getWindowAncestor( Table.this ), 
                    "Please select a cell before hitting Enter." );
        }
    }
    
    /**
     * Build a new SpreadSheet object with the data contained in this table and return the object
     */
    public SpreadSheet getSpreadSheet() {
        return ssh;
    }
    
    /**
     * Set the new data in this table. The previous data is cleared.
     * The columns will be increased if needed. Rows will be added if needed.
     */
    public final void setData( SpreadSheetCell[][] cells ) {
        if( cells != null ) {
            //make sure the table does not shrink below the minimum number of rows and columns
            if( cells.length < ROW_COUNT || cells[0].length < COL_COUNT + 1 ) {
                SpreadSheetCell[][] copy = 
                        new SpreadSheetCell[Math.max( cells.length, ROW_COUNT )][Math.max( cells[0].length, COL_COUNT + 1 )];
                for( int i = 0; i < Math.min( cells.length, copy.length ); i++ ) {
                    for( int j = 0; j < Math.min( cells[0].length, copy[0].length ); j++ ) {
                        copy[i][j] = cells[i][j];
                    }
                }
                cells = copy;
            }
            dtm = new DefaultTableModel( cells, generateColumnNames( Math.max( cells[0].length, COL_COUNT + 1 ) ) );
            setModel( dtm );
            ssh.setCells( cells );
            setMinSizeForColumns();
            FormulaParser.getInstance().setSpreadSheet( ssh );
            int iColCount = getColumnCount();
            while( cells[0].length > iColCount ) {//we assume we don't use ragged arrays
                dtm.addColumn( iColCount++ );//add columns if necessary
            }
            int iRowCount = getRowCount();//stash the row count to fill up the table later
            while( getRowCount() > 0 ) {
                dtm.removeRow( 0 );
            }
            int i = 0;
            for( ;i < cells.length; i++ ) {//add the rows
                Object[] obj = new Object[iColCount];
                System.arraycopy(cells[i], 1, obj, 1, cells[i].length - 1);
                dtm.addRow( obj );
            }
            for( int j = i; j < iRowCount; j++ ) {//pad the rest of the rows
                dtm.addRow( new SpreadSheetCell[cells[0].length] );//we assume we don't use ragged arrays
            }
            addRowsHeader();
        }
    }
    
    private void setMinSizeForColumns() {
        TableColumnModel tcm = getColumnModel();
        tcm.getColumn( 0 ).setMaxWidth( 50 );
        for( int i = 1; i < dtm.getColumnCount(); i++ ) {//minimum size for columns
            TableColumn tcl = tcm.getColumn( i );
            tcl.setCellEditor( new SpreadSheetCellEditor() );
            tcl.setMinWidth( 100 );
        }
    }
    
    private void notifyMainWindow() {
        if( iSelectedColumn > 0 ) {
            MainWindow mnw = ( ( MainWindow )SwingUtilities.getWindowAncestor( Table.this ) );
            mnw.setStatus( "Selected cell (" + ( iSelectedRow + 1 ) + ", " + getColumnName( iSelectedColumn ) + ")" );
            Object objCurrent = getValueAt( iSelectedRow, iSelectedColumn );
            if( objCurrent instanceof SpreadSheetCell ) {
                mnw.setTextFieldContent( ( ( SpreadSheetCell )objCurrent ).getCellValue() ); 
            } else {
                mnw.setTextFieldContent( objCurrent == null ? "" : objCurrent.toString() );
            }
        }
    }
    
    private String[] generateColumnNames( int iNoOfColumns ) {
        String[] staReturn = new String[iNoOfColumns];
        String[] staAlphabet = new String[] { "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N",
            "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" };
        staReturn[0] = "";
        for( int i = 0; i < iNoOfColumns - 1; i++ ) {
            int j = i / staAlphabet.length;
            if( j > 0 ) {
                staReturn[i + 1] = staAlphabet[j - 1] + staAlphabet[i % staAlphabet.length];
            } else {
                staReturn[i + 1] = staAlphabet[i % staAlphabet.length];
            }
        }
        return staReturn;
    }
    
    @Override
    public boolean isCellEditable( int row, int column ) {
       //first column is the rows header and it is not editable
       return column != 0;
    }
    
    private void addRowsHeader() {
        for( int i = 0; i < getRowCount(); i++ ) {
            setValueAt( (i+1) + "", i, 0 );
        }
        getColumn( "" ).setCellRenderer( new GreyCellRenderer() );
    }
    
    public class GreyCellRenderer extends DefaultTableCellRenderer {
        
        @Override
        public Component getTableCellRendererComponent( JTable table, Object value, 
                boolean isSelected, boolean hasFocus, int row, int col ) {
            Component cmp = super.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, col );
            cmp.setBackground( Color.lightGray );
            return cmp;
        }
    }
}